// Package slurp impliments reading files and resources.
package slurp

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"time"

	"gopkg.in/xmlpath.v2"

	"code.google.com/awwwe-browser/data"
	"code.google.com/awwwe-browser/scrub"
	"code.google.com/awwwe-browser/scrub/htmlsoup"
)

type ContentType int

const (
	ContentTypeNotHandled ContentType = iota
	ContentTypeHTML
	ContentTypeXML
	ContentTypeText
	ContentTypeRSS
	ContentTypeATOM
)

var contentTypes map[string]ContentType

const timeout time.Duration = time.Second * 15

var (
	feedStartTagBytes []byte = []byte("<feed")
	rssStartTagBytes  []byte = []byte("<rss")
)

func init() {
	contentTypes = make(map[string]ContentType)
	contentTypes["text/html"] = ContentTypeHTML
	contentTypes["text/xml"] = ContentTypeXML
	contentTypes["text/plain"] = ContentTypeText
}

// Returns the content type of markup which is a slurp.ContentType.
// If the type is text or xml then it checks to see if it is rss or atom.
func DetectContentType(markup []byte) ContentType {
	var stype string
	var splits []string
	var returnType ContentType
	var length int
	var ok bool
	var rsstagindex, atomtagindex int
	stype = http.DetectContentType(markup)
	splits = strings.Split(stype, "; ")
	if len(splits) > 0 {
		returnType, ok = contentTypes[splits[0]]
		if ok {
			if returnType == ContentTypeXML || returnType == ContentTypeText {
				length = len(markup)
				if length > 255 {
					length = 255
				}
				rsstagindex = bytes.Index(markup, rssStartTagBytes)
				atomtagindex = bytes.Index(markup, feedStartTagBytes)
				if rsstagindex > 0 && atomtagindex < 0 {
					returnType = ContentTypeRSS
				} else if rsstagindex < 0 && atomtagindex >= 0 {
					returnType = ContentTypeATOM
				} else {
					returnType = ContentTypeNotHandled
				}
			}
		} else {
			returnType = ContentTypeNotHandled
		}
	} else {
		returnType = ContentTypeNotHandled
	}
	return returnType
}

// Param path is the markup file path.
// Returns the normalized bytes of the file and the error.
func SlurpFile(path string) ([]byte, error) {
	var bb []byte
	var err error
	var file *os.File
	var fileinfo os.FileInfo
	var filesize int64
	file, err = os.Open(path)
	if err == nil {
		defer file.Close()
		fileinfo, err = file.Stat()
		if err == nil {
			filesize = fileinfo.Size()
			bb = make([]byte, filesize)
			_, err = file.Read(bb)
			return bb, err
		}
	}
	return data.EmptyBytes, err
}

// Param location is the uri.
// Param values is the name:vale pairs.
// Param cj is the cookiejar
// Uses the http get method.
// Returns the normalized bytes of the resource and the error.
func SlurpResourceGet(location string, values url.Values, cj http.CookieJar) ([]byte, ContentType, error) {
	var getlocation string
	getlocation = fmt.Sprintf("%s?%s", location, values.Encode())
	return SlurpResource(getlocation, cj)
}

// Param location is the uri.
// Param cj is the cookiejar
// Returns the normalized bytes of the resource and the error.
func SlurpResource(location string, cj http.CookieJar) ([]byte, ContentType, error) {
	var client http.Client
	var res *http.Response
	var err error
	var dirtymarkup []byte
	var returnType ContentType
	client.Timeout = time.Second * 15
	client.Jar = cj
	res, err = client.Get(location)
	if err == nil {
		if res.StatusCode == 200 {
			dirtymarkup, err = ioutil.ReadAll(res.Body)
			res.Body.Close()
			returnType = DetectContentType(dirtymarkup)
			return dirtymarkup, returnType, err
		} else {
			err = fmt.Errorf("error number %d", res.StatusCode)
		}
	}
	return data.EmptyBytes, ContentTypeNotHandled, err
}

// Param location is the uri.
// Param values is the name:vale pairs.
// Param cj is the cookiejar
// Uses the http post method.
// Returns the normalized bytes of the resource, the resource cocntent type and the error.
func SlurpResourcePost(location string, values url.Values, cj http.CookieJar) ([]byte, ContentType, error) {
	var client http.Client
	var res *http.Response
	var err error
	var dirtymarkup []byte
	var returnType ContentType
	client.Timeout = time.Second * 15
	client.Jar = cj
	res, err = client.PostForm(location, values)
	if err == nil {
		if res.StatusCode == 200 {
			dirtymarkup, err = ioutil.ReadAll(res.Body)
			res.Body.Close()
			returnType = DetectContentType(dirtymarkup)
			return dirtymarkup, returnType, err
		} else {
			err = fmt.Errorf("error number %d", res.StatusCode)
		}
	}
	return data.EmptyBytes, ContentTypeNotHandled, err
}

// Param markup is the markup from a resource.
// Returns the normalized version of markup and the error.
func SlurpXMLMarkupBytes(markup []byte) ([]byte, error) {
	var contentType string
	contentType = http.DetectContentType(markup)
	if contentType[:8] == "text/xml" || contentType[:10] == "text/plain" {
		return scrub.NormalizeBytes(markup), nil
	}
	return data.EmptyBytes, errors.New(fmt.Sprintf("Not XML. : %s", contentType))
}

// Param location is the markup uri.
// Returns the normalized bytes of the xml resource and the error.
func SlurpXMLMarkupUrl(location string) ([]byte, error) {
	var markup []byte
	var err error
	var res *http.Response
	res, err = http.Get(location)
	if err == nil {
		if res.StatusCode == 200 {
			markup, err = ioutil.ReadAll(res.Body)
			if err == nil {
				res.Body.Close()
				return SlurpXMLMarkupBytes(markup)
			}
		} else {
			err = fmt.Errorf("error number %d", res.StatusCode)
		}
	}
	return data.EmptyBytes, err
}

// Param path is the markup file path.
// Returns the normalized bytes of the xml file and the error.
func SlurpXMLMarkupFile(path string) ([]byte, error) {
	var markup []byte
	var err error
	var file *os.File
	var fileinfo os.FileInfo
	var nbytes int
	var filesize int64
	file, err = os.Open(path)
	if err == nil {
		defer file.Close()
		fileinfo, err = file.Stat()
		if err == nil {
			filesize = fileinfo.Size()
			markup = make([]byte, filesize)
			nbytes, err = file.Read(markup)
			// if nbytes == len(markup) {
			if err == nil {
				if nbytes > 2 && int64(nbytes) == filesize {
					return SlurpXMLMarkupBytes(markup)
				} else {
					return data.EmptyBytes, nil
				}
			} else {
				err = errors.New("Unable to read the entire vois.xml file.")
			}
		}
	}
	return data.EmptyBytes, err
}

// returns the bytes from the scrubbers version of node.
func nodeBytes(scrubber *scrub.Scrubber, node *xmlpath.Node) []byte {
	var compiled *xmlpath.Path
	var idnode *xmlpath.Node
	var iter *xmlpath.Iter
	var ok bool
	var idString string
	var idInt int
	var scrubbernode *htmlsoup.SoupNode
	compiled = xmlpath.MustCompile("@" + data.AWWWEAttributeId)
	iter = compiled.Iter(node)
	if iter.Next() {
		idnode = iter.Node()
		idString = idnode.String()
		idInt, ok = data.StringToInt(idString)
		if ok {
			// Use the id to get the node from the scrubber's tree
			scrubbernode, ok = scrubber.GetNodeFromId(idInt)
			if ok {
				return scrubbernode.Bytes()
			}
		}
	}
	return data.EmptyBytes
}
